package tp.pr5;

import tp.pr5.Interpreter;
import tp.pr5.instructions.Instruction;
import tp.pr5.instructions.exceptions.InstructionExecutionException;
import tp.pr5.items.InventoryObserver;
import tp.pr5.items.ItemContainer;

/**
 * This class represents the robot engine. It controls robot movements by processing the instructions introduced with the keyboard. The engine stops when the robot arrives at the spaceship or receives a quit instruction,
 * 
 */
public class RobotEngine extends Observable<RobotEngineObserver>
{
	private int fuel;
	private int recycledMaterial;
	
	private ItemContainer container;
	private NavigationModule navigationModule;
	private Direction direction;
	
	private boolean isOver = false;

	

	/**
	 * Creates the robot engine 
	 * 
	 * @param initialPlace
	 * @param direction
	 * @param cityMap
	 * 
	 */
	
	public RobotEngine(City cityMap, Place initialPlace, Direction direction)
	{
		this.direction = direction;
		this.navigationModule = new NavigationModule(cityMap, initialPlace);
		this.container = new ItemContainer();
	}

	/**
	 * FOR TESTING PURPOSES
	 * 
	 */
	public int getFuel()
	{
		return fuel;
	}

	/**
	 * FOR TESTING PURPOSES
	 * 
	 */
	public int getRecycledMaterial() 
	{
		return recycledMaterial;
	}

	/**
	 * Adds an amount of fuel to the robot (it can be negative)
	 * 
	 * @param fuel  Amount of fuel added to the robot
	 */
	
	public void addFuel(int fuel)
	{
		this.fuel += fuel;

		for (RobotEngineObserver Reo : this.lista)
		{
			Reo.robotUpdate(this.fuel, this.recycledMaterial);
		}

	}
	
	/**
	 * Increases the amount of recycled material of the robot
	 * 
	 * @param weight Amount of recycled material
	 */
	
	public void addRecycledMaterial(int weight)
	{
		this.recycledMaterial += weight;

		for (RobotEngineObserver Reo : this.lista)
		{
			Reo.robotUpdate(this.fuel, this.recycledMaterial);
		}
	}
	
	/**
	 * It executes an instruction. The instruction must be configured with the context before executing it. 
	 * It controls the end of the simulation. If the execution of the instruction throws an exception, then the corresponding message is printed
	 * 
	 * @param c The instruction to be executed
	 */
	
	public void communicateRobot(Instruction c)
	{
		try{
			
			c.configureContext(this, navigationModule, container);
			c.execute();
			
		}catch (InstructionExecutionException e){
			this.emitRaiseError(e.getMessage());
		}
		this.checkEnd();
	}
	
	/**
	 * Registers an EngineObserver to the model
	 * 
	 * @param observer - The observer that wants to be registered
	 */
	public void addEngineObserver(RobotEngineObserver observer) 
	{
		this.addObserver(observer);
	}
	
	/**
	 * Register a NavigationObserver to the model
	 * 
	 * @param robotObserver The observer that wants to be registered
	 */
	public void addNavigationObserver(NavigationObserver robotObserver)
	{
		this.navigationModule.addObserver(robotObserver);
	}
	
	/**
	 * Registers an ItemContainerObserver to the model
	 * 
	 * @param c The observer that wants to be registered
	 */
	public void addItemContainerObserver(InventoryObserver c)
	{
		this.container.addObserver(c);
	}
	
	/**
	 * Requests the engine to inform about all possible instructions
	 * 
	 */
	public void requestHelp()
	{
		for (RobotEngineObserver Reo : this.lista)
		{
			Reo.communicationHelp(Interpreter.interpreterHelp());
		}
	}
	
	/**
	 * Request the engine to say something
	 * 
	 * @param message The message to say
	 */
	public void saySomething(String message)
	{
		for (RobotEngineObserver Reo : this.lista)
		{
			Reo.robotSays(message);
		}
	}
	
	/**
	 * Checks if the simulation is finished
	 * 
	 * @return true if the game has finished
	 */
	public boolean isOver()
	{
		return this.isOver;
	}
	
	/**
	 * Checks the end
	 * 
	 */
	public void checkEnd()
	{
		if(this.fuel <= 0)
		{
			this.emitEngineOff(false);
			this.isOver = true;
		}
		else if(this.navigationModule.atSpaceship())
		{
			this.emitEngineOff(true);
			this.isOver = true;
		}
	}
	
	/**
	 * Requests the engine to inform the observers that the simulation starts
	 * 
	 */
	public void requestStart()
	{
		this.fuel = 100;
		this.recycledMaterial = 0;
		this.navigationModule.initHeading(direction);
		this.emitRobotUpdate(fuel, recycledMaterial);
	}

	/**
	 * Requests the end of the simulation
	 * 
	 */
	public void requestQuit()
	{
		this.emitCommunicationCompleted();
		this.isOver = true;
		System.exit(0);
	}
	
	/**
	 * Executes robotUpdate for those who are observing the RobotEngineObserver
	 * 
	 * @param fuel of the robot
	 * @param material of the robot
	 */
	public void emitRobotUpdate(int fuel, int material)
	{
		for (RobotEngineObserver roObserver : this.lista) 
		{
			roObserver.robotUpdate(fuel, material);
		}
	}
	
	/**
	 * The robot engine informs that it has raised an error
	 * 
	 * @param msg Error message
	 */
	public void emitRaiseError(String msg)
	{
		for (RobotEngineObserver roObserver : this.lista) 
		{
			roObserver.raiseError(msg);
		}
	}
	
	/**
	 * The robot engine informs that the fuel and/or the amount of recycled material has changed
	 * 
	 */
	public void emitCommunicationCompleted()
	{
		for (RobotEngineObserver roObserver : this.lista) 
		{
			roObserver.communicationCompleted();
		}
	}
	
	/**
	 * The robot engine informs that the communication is over.
	 * 
	 * @param isOver true if the simulation ends
	 */
	public void emitEngineOff(boolean isOver)
	{
		for (RobotEngineObserver roObserver : this.lista) 
		{
			roObserver.engineOff(isOver);
		}
	}

}


